package com.starview.star.compiler.canonical;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import com.starview.platform.data.type.Freshen;
import com.starview.platform.data.type.IType;
import com.starview.platform.data.type.Location;
import com.starview.platform.data.type.Quantifier;
import com.starview.platform.data.type.TypeUtils;
import com.starview.star.compiler.cafe.compile.Utils;
import com.starview.star.compiler.standard.StandardNames;
import com.starview.star.compiler.transform.OverLoader;
import com.starview.star.compiler.util.AccessMode;
import com.starview.star.compiler.util.GenSym;
import com.starview.star.compiler.util.Pair;
import com.starview.star.compiler.util.PrettyPrintDisplay;

/**
 * The Variable references a local variable. A variable that is part of a content expression. It can
 * denote either an expression or a pattern.
 * 
 * @author fgm
 * 
 */
@SuppressWarnings("serial")
public class Variable extends BaseExpression implements IContentPattern, Comparable<Variable>
{
  private final String name;

  public Variable(Location loc, IType type, String name)
  {
    super(loc, type);
    this.name = name;
  }

  public static Variable create(Location loc, IType type, String name)
  {
    if (TypeUtils.hasContractDependencies(type))
      return new OverloadedVariable(loc, type, OverLoader.computeDictionaryType(type, loc, AccessMode.readOnly), name);
    else
      return new Variable(loc, type, name);
  }

  // create a new variable with a different type but everything else the same.
  public Variable copy()
  {
    return new Variable(getLoc(), getType(), GenSym.genSym(getName()));
  }

  // opposite of an overload
  public Variable underLoad()
  {
    return this;
  }

  public String getName()
  {
    return name;
  }

  public Pair<Variable, Map<String, Quantifier>> freshen(Location loc)
  {
    Pair<IType, Map<String, Quantifier>> f = Freshen.freshen(getType(), AccessMode.readOnly, AccessMode.readWrite);
    return Pair.pair(new Variable(loc, f.left, getName()), f.right);
  }

  @Override
  public void prettyPrint(PrettyPrintDisplay disp)
  {
    disp.appendId(name);
  }

  @Override
  public void accept(CanonicalVisitor visitor)
  {
    visitor.visitVariable(this);
  }

  @Override
  public <A, E, P, C, D, T> E transform(TransformExpression<A, E, P, C, D, T> transform, T context)
  {
    return transform.transformVariable(this, context);
  }

  @Override
  public <A, E, P, C, D, T> P transformPattern(TransformPattern<A, E, P, C, D, T> transform, T context)
  {
    return transform.transformVariablePtn(this, context);
  }

  @Override
  public boolean equals(Object obj)
  {
    return obj instanceof Variable && ((Variable) obj).getName().equals(getName()) && ((Variable) obj).isRealVariable();
  }

  @Override
  public int hashCode()
  {
    return getName().hashCode();
  }

  @Override
  public int compareTo(Variable o)
  {
    return getName().compareTo(o.getName());
  }

  public static Variable anonymous(Location loc, IType type)
  {
    return new Variable(loc, type, GenSym.genSym(StandardNames.ANONYMOUS_PREFIX));
  }

  public static boolean isAnonymous(Variable var)
  {
    String label = var.getName();
    return Utils.isAnonymous(label);
  }

  public boolean isRealVariable()
  {
    return true;
  }

  public static List<String> varNames(Collection<Variable> defined)
  {
    List<String> names = new ArrayList<>(defined.size());
    for (Variable v : defined)
      names.add(v.getName());
    return names;
  }
}
